Installing packages

install.packages("ggsoccer")
Error in install.packages : Updating loaded packages
install.packages("tidyverse")
Installing package into ‘C:/Users/kusha/AppData/Local/R/win-library/4.3’
(as ‘lib’ is unspecified)
trying URL 'https://cran.rstudio.com/bin/windows/contrib/4.3/tidyverse_2.0.0.zip'
Content type 'application/zip' length 430846 bytes (420 KB)
downloaded 420 KB
package ‘tidyverse’ successfully unpacked and MD5 sums checked

The downloaded binary packages are in
    C:\Users\kusha\AppData\Local\Temp\Rtmpuy3JWL\downloaded_packages
install.packages("ggsoccer")
Installing package into ‘C:/Users/kusha/AppData/Local/R/win-library/4.3’
(as ‘lib’ is unspecified)
trying URL 'https://cran.rstudio.com/bin/windows/contrib/4.3/ggsoccer_0.1.7.zip'
Content type 'application/zip' length 288927 bytes (282 KB)
downloaded 282 KB
package ‘ggsoccer’ successfully unpacked and MD5 sums checked

The downloaded binary packages are in
    C:\Users\kusha\AppData\Local\Temp\Rtmpuy3JWL\downloaded_packages
install.packages("ggplot2")
Error in install.packages : Updating loaded packages
install.packages("here")
Installing package into ‘C:/Users/kusha/AppData/Local/R/win-library/4.3’
(as ‘lib’ is unspecified)
trying URL 'https://cran.rstudio.com/bin/windows/contrib/4.3/here_1.0.1.zip'
Content type 'application/zip' length 64152 bytes (62 KB)
downloaded 62 KB
package ‘here’ successfully unpacked and MD5 sums checked

The downloaded binary packages are in
    C:\Users\kusha\AppData\Local\Temp\Rtmpuy3JWL\downloaded_packages
install.packages("devtools")
Error in install.packages : Updating loaded packages
install.packages("remotes")
Error in install.packages : Updating loaded packages
devtools::install_github("cran/SDMTools")
Skipping install of 'SDMTools' from a github remote, the SHA1 (4f193c85) has not changed since last install.
  Use `force = TRUE` to force installation
devtools::install_github("statsbomb/StatsBombR")
Skipping install of 'StatsBombR' from a github remote, the SHA1 (f0503b80) has not changed since last install.
  Use `force = TRUE` to force installation
install.packages("remotes")
Installing package into ‘C:/Users/kusha/AppData/Local/R/win-library/4.3’
(as ‘lib’ is unspecified)
trying URL 'https://cran.rstudio.com/bin/windows/contrib/4.3/remotes_2.4.2.1.zip'
Content type 'application/zip' length 398944 bytes (389 KB)
downloaded 389 KB
package ‘remotes’ successfully unpacked and MD5 sums checked

The downloaded binary packages are in
    C:\Users\kusha\AppData\Local\Temp\Rtmpuy3JWL\downloaded_packages
install.packages("devtools")
Installing package into ‘C:/Users/kusha/AppData/Local/R/win-library/4.3’
(as ‘lib’ is unspecified)
trying URL 'https://cran.rstudio.com/bin/windows/contrib/4.3/devtools_2.4.5.zip'
Content type 'application/zip' length 436170 bytes (425 KB)
downloaded 425 KB
package ‘devtools’ successfully unpacked and MD5 sums checked

The downloaded binary packages are in
    C:\Users\kusha\AppData\Local\Temp\Rtmpuy3JWL\downloaded_packages
install.packages("ggplot2")
Error in install.packages : Updating loaded packages
devtools::install_github("jogall/soccermatics")
Skipping install of 'soccermatics' from a github remote, the SHA1 (4dfbfebc) has not changed since last install.
  Use `force = TRUE` to force installation
install.packages("ggplot2")
Installing package into ‘C:/Users/kusha/AppData/Local/R/win-library/4.3’
(as ‘lib’ is unspecified)
trying URL 'https://cran.rstudio.com/bin/windows/contrib/4.3/ggplot2_3.4.4.zip'
Content type 'application/zip' length 4297844 bytes (4.1 MB)
downloaded 4.1 MB
package ‘ggplot2’ successfully unpacked and MD5 sums checked

The downloaded binary packages are in
    C:\Users\kusha\AppData\Local\Temp\Rtmpuy3JWL\downloaded_packages
library(ggsoccer)
Warning: package ‘ggsoccer’ was built under R version 4.3.2
library(tidyverse)
Warning: package ‘tidyverse’ was built under R version 4.3.2Warning: package ‘ggplot2’ was built under R version 4.3.2── Attaching core tidyverse packages ──────────────────────────────────────────────────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.1.3     ✔ readr     2.1.4
✔ forcats   1.0.0     ✔ stringr   1.5.0
✔ ggplot2   3.4.4     ✔ tibble    3.2.1
✔ lubridate 1.9.3     ✔ tidyr     1.3.0
✔ purrr     1.0.2     ── Conflicts ────────────────────────────────────────────────────────────────────────────────────── tidyverse_conflicts() ──
✖ tidyr::expand() masks Matrix::expand()
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
✖ tidyr::pack()   masks Matrix::pack()
✖ dplyr::recode() masks arules::recode()
✖ tidyr::unpack() masks Matrix::unpack()
ℹ Use the ]8;;http://conflicted.r-lib.org/conflicted package]8;; to force all conflicts to become errors
library(here)
Warning: package ‘here’ was built under R version 4.3.2here() starts at D:/Football-Analytics
library(ggplot2)
library(StatsBombR)
Loading required package: stringi
Loading required package: rvest

Attaching package: ‘rvest’

The following object is masked from ‘package:readr’:

    guess_encoding

Loading required package: RCurl

Attaching package: ‘RCurl’

The following object is masked from ‘package:tidyr’:

    complete

Loading required package: doParallel
Loading required package: foreach

Attaching package: ‘foreach’

The following objects are masked from ‘package:purrr’:

    accumulate, when

Loading required package: iterators
Loading required package: parallel
Loading required package: httr
Loading required package: jsonlite

Attaching package: ‘jsonlite’

The following object is masked from ‘package:purrr’:

    flatten

Loading required package: sp
The legacy packages maptools, rgdal, and rgeos, underpinning the sp package,
which was just loaded, were retired in October 2023.
Please refer to R-spatial evolution reports for details, especially
https://r-spatial.org/r/2023/05/15/evolution4.html.
It may be desirable to make the sf package available;
package maintainers should consider adding sf to Suggests:.
Warning: replacing previous import ‘jsonlite::flatten’ by ‘purrr::flatten’ when loading ‘StatsBombR’Warning: replacing previous import ‘foreach::when’ by ‘purrr::when’ when loading ‘StatsBombR’Warning: replacing previous import ‘foreach::accumulate’ by ‘purrr::accumulate’ when loading ‘StatsBombR’
library(soccermatics)

Data and Pre-processing

View all the competitions available in the StatsBomb dataset (Free edition)

FreeCompetitions()
[1] "Whilst we are keen to share data and facilitate research, we also urge you to be responsible with the data. Please credit StatsBomb as your data source when using the data and visit https://statsbomb.com/media-pack/ to obtain our logos for public use."

Selecting FIFA World Cup 2022

comps <- FreeCompetitions() %>%
filter(competition_id==43 & season_name=="2022")
[1] "Whilst we are keen to share data and facilitate research, we also urge you to be responsible with the data. Please credit StatsBomb as your data source when using the data and visit https://statsbomb.com/media-pack/ to obtain our logos for public use."
comps

All the match event data available for this tournament

matches <- FreeMatches(Competitions = comps)
[1] "Whilst we are keen to share data and facilitate research, we also urge you to be responsible with the data. Please credit StatsBomb as your data source when using the data and visit https://statsbomb.com/media-pack/ to obtain our logos for public use."
matches

Choosing the World Cup Finals 2022

events <- get.matchFree(matches[10,])
[1] "Whilst we are keen to share data and facilitate research, we also urge you to be responsible with the data. Please credit StatsBomb as your data source when using the data and visit https://statsbomb.com/media-pack/ to obtain our logos for public use."
events

Join all the data for this match

cleanevents = allclean(events)
Joining with `by = join_by(id)`Joining with `by = join_by(id)`Joining with `by = join_by(id)`Joining with `by = join_by(id)`Joining with `by = join_by(id)`Joining with `by = join_by(id)`Joining with `by = join_by(id)`Joining with `by = join_by(id)`Joining with `by = join_by(id)`Joining with `by = join_by(id)`Joining with `by = join_by(id)`Joining with `by = join_by(id)`Joining with `by = join_by(id)`Joining with `by = join_by(id)`Joining with `by = join_by(id)`Joining with `by = join_by(id)`Joining with `by = join_by(id)`Joining with `by = join_by(period, match_id)`
cleanevents

Identify player

# Converting player name for representation purpose
cleanevents <- cleanevents %>%
  mutate(player.name = ifelse(player.name == 'Lionel Andrés Messi Cuccittini', 'Lionel Messi', player.name))

# Player credentials
player_id <- 5503
player_name <- 'Lionel Messi'

Passes

Pre-processing - Retrieving location based on period

process_period <- function(half) {
  filtered_events <- cleanevents %>%
  filter(type.name == 'Pass' & period==half & player.id==player_id) %>%
  select(location, pass.end_location)

  locationX <- lapply(filtered_events$location, function(x) x[[1]])
  locationY <- lapply(filtered_events$location, function(x) x[[2]])
  endLocationX <- lapply(filtered_events$pass.end_location, function(x) x[[1]])
  endLocationY <- lapply(filtered_events$pass.end_location, function(x) x[[2]])

  filtered_passes <- data.frame(
    LocationX = unlist(locationX),
    LocationY = unlist(locationY),
    EndLocationX = unlist(endLocationX),
    EndLocationY = unlist(endLocationY)
  )

  return (filtered_passes)
}

First Half (Normal time)

filtered_passes <- process_period(1)

to_statsbomb <- rescale_coordinates(from = pitch_opta, to = pitch_statsbomb)

passes_rescaled <- data.frame(x  = to_statsbomb$x(filtered_passes$LocationX),
                              y  = to_statsbomb$y(filtered_passes$LocationY),
                              x2 = to_statsbomb$x(filtered_passes$EndLocationX),
                              y2 = to_statsbomb$y(filtered_passes$EndLocationY))

ggplot(passes_rescaled) +
  annotate_pitch(dimensions = pitch_statsbomb, , colour = "white", fill   = "springgreen4") +
  geom_segment(aes(x = x, y = y, xend = x2, yend = y2),
               colour = "coral",
               arrow = arrow(length = unit(0.25, "cm"),
                             type = "closed")) +
   geom_point(aes(x = x, y = y),
             colour = "yellow",
             size = 4) +
  theme_pitch() +
  direction_label(x_label = 60) +
  theme(panel.background = element_rect(fill = "springgreen4"))+
  ggtitle("PassMap",
          "Messi in the First Half (Normal time)")

Second Half (Normal time)

filtered_passes <- process_period(2)
to_statsbomb <- rescale_coordinates(from = pitch_opta, to = pitch_statsbomb)

passes_rescaled <- data.frame(x  = to_statsbomb$x(filtered_passes$LocationX),
                              y  = to_statsbomb$y(filtered_passes$LocationY),
                              x2 = to_statsbomb$x(filtered_passes$EndLocationX),
                              y2 = to_statsbomb$y(filtered_passes$EndLocationY))

ggplot(passes_rescaled) +
  annotate_pitch(dimensions = pitch_statsbomb, , colour = "white", fill   = "springgreen4") +
  geom_segment(aes(x = x, y = y, xend = x2, yend = y2),
               colour = "coral",
               arrow = arrow(length = unit(0.25, "cm"),
                             type = "closed")) +
   geom_point(aes(x = x, y = y),
             colour = "yellow",
             size = 4) +
  theme_pitch() +
  direction_label(x_label = 60) +
  theme(panel.background = element_rect(fill = "springgreen4"))+
  ggtitle("PassMap",
          "Messi in the Second Half (Normal time)")

First Half (Extra time)

filtered_passes <- process_period(3)

to_statsbomb <- rescale_coordinates(from = pitch_opta, to = pitch_statsbomb)

passes_rescaled <- data.frame(x  = to_statsbomb$x(filtered_passes$LocationX),
                              y  = to_statsbomb$y(filtered_passes$LocationY),
                              x2 = to_statsbomb$x(filtered_passes$EndLocationX),
                              y2 = to_statsbomb$y(filtered_passes$EndLocationY))

ggplot(passes_rescaled) +
  annotate_pitch(dimensions = pitch_statsbomb, , colour = "white", fill   = "springgreen4") +
  geom_segment(aes(x = x, y = y, xend = x2, yend = y2),
               colour = "coral",
               arrow = arrow(length = unit(0.25, "cm"),
                             type = "closed")) +
   geom_point(aes(x = x, y = y),
             colour = "yellow",
             size = 4) +
  theme_pitch() +
  direction_label(x_label = 60) +
  theme(panel.background = element_rect(fill = "springgreen4"))+
  ggtitle("PassMap",
          "Messi in the First Half (Extra time)")

Second Half (Extra time)

filtered_passes <- process_period(4)

to_statsbomb <- rescale_coordinates(from = pitch_opta, to = pitch_statsbomb)

passes_rescaled <- data.frame(x  = to_statsbomb$x(filtered_passes$LocationX),
                              y  = to_statsbomb$y(filtered_passes$LocationY),
                              x2 = to_statsbomb$x(filtered_passes$EndLocationX),
                              y2 = to_statsbomb$y(filtered_passes$EndLocationY))

ggplot(passes_rescaled) +
  annotate_pitch(dimensions = pitch_statsbomb, , colour = "white", fill   = "springgreen4") +
  geom_segment(aes(x = x, y = y, xend = x2, yend = y2),
               colour = "coral",
               arrow = arrow(length = unit(0.25, "cm"),
                             type = "closed")) +
   geom_point(aes(x = x, y = y),
             colour = "yellow",
             size = 4) +
  theme_pitch() +
  direction_label(x_label = 60) +
  theme(panel.background = element_rect(fill = "springgreen4"))+
  ggtitle("PassMap",
          "Messi in the Second Half (Extra time)")

Pass Position

Average

cleanevents %>%
  filter(player.id==player_id & type.name == "Pass") %>%
  soccerTransform(method='statsbomb') %>%
  soccerPositionMap(id = "player.name", x = "location.x", y = "location.y",
                    fill1 = "blue",
                    arrow = "r",
                    theme = "grass",
                    title = "Average Pass Postition",
                    subtitle='Messi vs France')

Favourite

 cleanevents %>%
  filter(player.id == player_id) %>%
  soccerPassmap(fill = "lightblue", arrow = "r", theme='grass',
                title = "Highest Pass Position")
Warning: There was 1 warning in `mutate()`.
ℹ In argument: `pass.outcome.name = fct_explicit_na(pass.outcome.name, "Complete")`.
Caused by warning:
! `fct_explicit_na()` was deprecated in forcats 1.0.0.
ℹ Please use `fct_na_value_to_level()` instead.
ℹ The deprecated feature was likely used in the soccermatics package.
  Please report the issue to the authors.
This warning is displayed once every 8 hours.
Call `lifecycle::last_lifecycle_warnings()` to see where this warning was generated.

Comparison

cleanevents %>%
  filter(team.name=='Argentina' & type.name == "Pass") %>%
  soccerTransform(method='statsbomb') %>%
  soccerPositionMap(id = "player.name", x = "location.x", y = "location.y",
                    fill1 = "blue",
                    arrow = "r",
                    theme = "grass",
                    title = "Average Pass Postition",
                    subtitle='Argentina')

Shots

Comparison among teams

summary <- cleanevents %>%
  group_by("Team"=cleanevents$team.name) %>%
  summarise(shots = sum(type.name=="Shot", na.rm = TRUE))
summary

Comparison among Argentinian players

filtered_events <- cleanevents %>%
  filter(team.name == 'Argentina' & type.name == 'Shot') %>%
  select(player.name)

shots <- filtered_events %>%
  group_by(player.name) %>%
  summarise(shots = n())

print(shots)

Messi’s ShotMap

  • 23’ (P)
  • 108’
  • Shootouts
cleanevents %>%
  filter(player.id==player_id) %>%
  soccerTransform(method='statsbomb') %>%
  soccerShotmap(theme = "grass", title = "ShotMap",
                subtitle = "Shots taken by Messi")

Messi’s Average ShotMap

cleanevents %>%
  filter(player.id==player_id & type.name == "Shot") %>%
  soccerTransform(method='statsbomb') %>%
  soccerPositionMap(id = "player.name", x = "location.x", y = "location.y",
                    fill1 = "blue",
                    arrow = "r",
                    theme = "grass",
                    title = "Average Shot Postition",
                    subtitle = 'Messi vs France')

Movement

HeatMap

cleanevents %>%
  filter(player.id==player_id) %>%
  soccerTransform(method='statsbomb') %>%
  soccerHeatmap(x = "location.x", y = "location.y", xBins = 21, yBins = 14,
                title = "HeatMap",
                subtitle = 'Messi vs France')

Model

Pre-processing

data <- cleanevents %>%
  select(duration, player.name)
filtered_events_model <- cleanevents %>%
  filter(team.name=='Argentina') %>%
  select(duration, player.name)

data_clean <- filtered_events_model[complete.cases(filtered_events_model$duration, filtered_events_model$player.name), ]

print(data_clean)
duration_data <- data.frame(total_duration = double(), player_name = character(), stringsAsFactors = FALSE)
process_period <- function(player_name) {
  filtered_events <- data_clean %>%
  filter(player.name==player_name) %>%
  select(duration)

  total_duration_player <- sum(filtered_events$duration, na.rm = TRUE)

  new_row <- data.frame(total_duration = total_duration_player, player_name = player_name, stringsAsFactors = FALSE)
  return (new_row)
}
unique_players <- unique(data_clean$player.name)


for (player_name in unique_players) {
  new_row <- process_period(player_name)
  duration_data <- rbind(duration_data, new_row)

}

print(duration_data)
#Total shots by each player
shots <- cleanevents %>%
  filter(team.name == 'Argentina' & type.name == 'Shot') %>%
  group_by(player.name) %>%
  summarise(shots = n())

# Create a data frame with all player names
all_players <- data.frame(player.name = unique(cleanevents$player.name[cleanevents$team.name == 'Argentina']))

# Left join with shots summary, replace NA with 0
merged_data <- left_join(all_players, shots, by = "player.name") %>%
  mutate(shots = ifelse(is.na(shots), 0, shots))

# Print the result
print(merged_data)
NA
# Assuming merged_data and duration_data are your data frames
# merged_data contains columns player.name and shots
# duration_data contains columns player_name and total_duration

# Left join merged_data with duration_data
final_merged_data <- left_join(merged_data, duration_data, by = c("player.name" = "player_name"))

# If needed, replace NA values in shots and total_duration with 0
final_merged_data$shots[is.na(final_merged_data$shots)] <- 0
final_merged_data$total_duration[is.na(final_merged_data$total_duration)] <- 0

# Print the final merged data
print(final_merged_data)
NA
NA
# Filter and count passes
passes <- cleanevents %>%
  filter(team.name == 'Argentina' & type.name == 'Pass') %>%
  group_by(player.name) %>%
  summarise(passes = n())

# Merge with the existing final_merged_data
final_merged_data <- left_join(final_merged_data, passes, by = c("player.name" = "player.name"))

# If needed, replace NA values in passes with 0
final_merged_data$passes[is.na(final_merged_data$passes)] <- 0

# Print the final merged data
print(final_merged_data)
NA

k-means Clustering

selected_columns <- final_merged_data[, c("shots", "total_duration", "passes")]

# Standardize the data
scaled_data <- scale(selected_columns)

k <- 3  
kmeans_result <- kmeans(scaled_data, centers = k, nstart = 20)

# Add the cluster assignments back to the original data frame
final_merged_data$cluster <- kmeans_result$cluster

# Print the cluster centroids
print(kmeans_result$centers)
          shots total_duration    passes
1  9.251859e-17     -1.1440579 -1.106724
2 -5.703224e-01      0.2931336  0.311266
3  1.140645e+00      1.1298197  1.037553
# View the distribution of players in each cluster
table(final_merged_data$cluster)

1 2 3 
6 8 4 
library(ggplot2)

# Scatter plot of total_duration against shots, colored by cluster, with player names
ggplot(final_merged_data, aes(x = total_duration, y = shots, color = factor(cluster), label = player.name)) +
  geom_point(aes(size = passes)) +
  geom_text(nudge_x = 0.2, nudge_y = 0.2, size = 3) +  # Adjust nudge values as needed
  labs(title = "K-Means Clustering of Players", x = "Total Duration", y = "Shots", color = "Cluster") +
  theme_minimal()


# Draw circles around each cluster
cluster_centers <- as.data.frame(kmeans_result$centers[, c("total_duration", "shots")])
library(ggplot2)

# Scatter plot of total_duration against shots, colored by cluster, with player names
ggplot(final_merged_data, aes(x = passes, y = shots, color = factor(cluster), label = player.name)) +
  geom_point(aes(size = total_duration)) +
  geom_text(nudge_x = 0.2, nudge_y = 0.2, size = 3) +  # Adjust nudge values as needed
  labs(title = "K-Means Clustering of Players", x = "Passes", y = "Shots", color = "Cluster") +
  theme_minimal()


# Draw circles around each cluster
cluster_centers <- as.data.frame(kmeans_result$centers[, c("passes", "shots")])
library(plotly)

# Create a 3D scatter plot
p <- plot_ly(final_merged_data, x = ~total_duration, y = ~shots, z = ~passes, color = ~factor(cluster)) %>%
  add_markers(size = 2)  %>%

# Add labels for player names
  add_text(text = ~player.name, 
           showlegend = FALSE,
           hoverinfo = "text",
           size = 1)
# Show the plot
p
NA
NA
NA
LS0tDQp0aXRsZTogIkZvb3RiYWxsIEFuYWx5dGljcyINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCiMgKipJbnN0YWxsaW5nIHBhY2thZ2VzKioNCg0KLSAgIFN0YXRzQm9tYiAtIEZvciBtYXRjaCBldmVudCBkYXRhDQoNCi0gICBzb2NjZXJtYXRpY3MgLSBQaXRjaCBwbG90cw0KDQotICAgZ2dzb2NjZXIgLSBQaXRjaCBwbG90cw0KDQpgYGB7cn0NCmluc3RhbGwucGFja2FnZXMoImdnc29jY2VyIikNCmluc3RhbGwucGFja2FnZXMoInRpZHl2ZXJzZSIpDQppbnN0YWxsLnBhY2thZ2VzKCJnZ3Bsb3QyIikNCmluc3RhbGwucGFja2FnZXMoImhlcmUiKQ0KDQppbnN0YWxsLnBhY2thZ2VzKCJkZXZ0b29scyIpDQppbnN0YWxsLnBhY2thZ2VzKCJyZW1vdGVzIikNCg0KZGV2dG9vbHM6Omluc3RhbGxfZ2l0aHViKCJjcmFuL1NETVRvb2xzIikNCmRldnRvb2xzOjppbnN0YWxsX2dpdGh1Yigic3RhdHNib21iL1N0YXRzQm9tYlIiKQ0KZGV2dG9vbHM6Omluc3RhbGxfZ2l0aHViKCJqb2dhbGwvc29jY2VybWF0aWNzIikNCg0KDQpgYGANCg0KYGBge3J9DQpsaWJyYXJ5KGdnc29jY2VyKQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KGhlcmUpDQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KFN0YXRzQm9tYlIpDQpsaWJyYXJ5KHNvY2Nlcm1hdGljcykNCmBgYA0KDQojICoqRGF0YSBhbmQgUHJlLXByb2Nlc3NpbmcqKg0KDQojIyBWaWV3IGFsbCB0aGUgY29tcGV0aXRpb25zIGF2YWlsYWJsZSBpbiB0aGUgU3RhdHNCb21iIGRhdGFzZXQgKEZyZWUgZWRpdGlvbikNCg0KYGBge3J9DQpGcmVlQ29tcGV0aXRpb25zKCkNCmBgYA0KDQojIyBTZWxlY3RpbmcgRklGQSBXb3JsZCBDdXAgMjAyMg0KDQpgYGB7cn0NCmNvbXBzIDwtIEZyZWVDb21wZXRpdGlvbnMoKSAlPiUNCmZpbHRlcihjb21wZXRpdGlvbl9pZD09NDMgJiBzZWFzb25fbmFtZT09IjIwMjIiKQ0KY29tcHMNCmBgYA0KDQojIyBBbGwgdGhlIG1hdGNoIGV2ZW50IGRhdGEgYXZhaWxhYmxlIGZvciB0aGlzIHRvdXJuYW1lbnQNCg0KYGBge3J9DQptYXRjaGVzIDwtIEZyZWVNYXRjaGVzKENvbXBldGl0aW9ucyA9IGNvbXBzKQ0KbWF0Y2hlcw0KYGBgDQoNCiMjIENob29zaW5nIHRoZSBXb3JsZCBDdXAgRmluYWxzIDIwMjINCg0KYGBge3J9DQpldmVudHMgPC0gZ2V0Lm1hdGNoRnJlZShtYXRjaGVzWzEwLF0pDQpldmVudHMNCmBgYA0KDQojIyBKb2luIGFsbCB0aGUgZGF0YSBmb3IgdGhpcyBtYXRjaA0KDQpgYGB7cn0NCmNsZWFuZXZlbnRzID0gYWxsY2xlYW4oZXZlbnRzKQ0KY2xlYW5ldmVudHMNCmBgYA0KDQojIyBJZGVudGlmeSBwbGF5ZXINCg0KYGBge3J9DQojIENvbnZlcnRpbmcgcGxheWVyIG5hbWUgZm9yIHJlcHJlc2VudGF0aW9uIHB1cnBvc2UNCmNsZWFuZXZlbnRzIDwtIGNsZWFuZXZlbnRzICU+JQ0KICBtdXRhdGUocGxheWVyLm5hbWUgPSBpZmVsc2UocGxheWVyLm5hbWUgPT0gJ0xpb25lbCBBbmRyw6lzIE1lc3NpIEN1Y2NpdHRpbmknLCAnTGlvbmVsIE1lc3NpJywgcGxheWVyLm5hbWUpKQ0KDQojIFBsYXllciBjcmVkZW50aWFscw0KcGxheWVyX2lkIDwtIDU1MDMNCnBsYXllcl9uYW1lIDwtICdMaW9uZWwgTWVzc2knDQpgYGANCg0KIyBQYXNzZXMNCg0KIyMgUHJlLXByb2Nlc3NpbmcgLSBSZXRyaWV2aW5nIGxvY2F0aW9uIGJhc2VkIG9uIHBlcmlvZA0KDQpgYGB7cn0NCnByb2Nlc3NfcGVyaW9kIDwtIGZ1bmN0aW9uKGhhbGYpIHsNCiAgZmlsdGVyZWRfZXZlbnRzIDwtIGNsZWFuZXZlbnRzICU+JQ0KICBmaWx0ZXIodHlwZS5uYW1lID09ICdQYXNzJyAmIHBlcmlvZD09aGFsZiAmIHBsYXllci5pZD09cGxheWVyX2lkKSAlPiUNCiAgc2VsZWN0KGxvY2F0aW9uLCBwYXNzLmVuZF9sb2NhdGlvbikNCg0KICBsb2NhdGlvblggPC0gbGFwcGx5KGZpbHRlcmVkX2V2ZW50cyRsb2NhdGlvbiwgZnVuY3Rpb24oeCkgeFtbMV1dKQ0KICBsb2NhdGlvblkgPC0gbGFwcGx5KGZpbHRlcmVkX2V2ZW50cyRsb2NhdGlvbiwgZnVuY3Rpb24oeCkgeFtbMl1dKQ0KICBlbmRMb2NhdGlvblggPC0gbGFwcGx5KGZpbHRlcmVkX2V2ZW50cyRwYXNzLmVuZF9sb2NhdGlvbiwgZnVuY3Rpb24oeCkgeFtbMV1dKQ0KICBlbmRMb2NhdGlvblkgPC0gbGFwcGx5KGZpbHRlcmVkX2V2ZW50cyRwYXNzLmVuZF9sb2NhdGlvbiwgZnVuY3Rpb24oeCkgeFtbMl1dKQ0KDQogIGZpbHRlcmVkX3Bhc3NlcyA8LSBkYXRhLmZyYW1lKA0KICAgIExvY2F0aW9uWCA9IHVubGlzdChsb2NhdGlvblgpLA0KICAgIExvY2F0aW9uWSA9IHVubGlzdChsb2NhdGlvblkpLA0KICAgIEVuZExvY2F0aW9uWCA9IHVubGlzdChlbmRMb2NhdGlvblgpLA0KICAgIEVuZExvY2F0aW9uWSA9IHVubGlzdChlbmRMb2NhdGlvblkpDQogICkNCg0KICByZXR1cm4gKGZpbHRlcmVkX3Bhc3NlcykNCn0NCmBgYA0KDQojIyBGaXJzdCBIYWxmIChOb3JtYWwgdGltZSkNCg0KYGBge3J9DQpmaWx0ZXJlZF9wYXNzZXMgPC0gcHJvY2Vzc19wZXJpb2QoMSkNCg0KdG9fc3RhdHNib21iIDwtIHJlc2NhbGVfY29vcmRpbmF0ZXMoZnJvbSA9IHBpdGNoX29wdGEsIHRvID0gcGl0Y2hfc3RhdHNib21iKQ0KDQpwYXNzZXNfcmVzY2FsZWQgPC0gZGF0YS5mcmFtZSh4ICA9IHRvX3N0YXRzYm9tYiR4KGZpbHRlcmVkX3Bhc3NlcyRMb2NhdGlvblgpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeSAgPSB0b19zdGF0c2JvbWIkeShmaWx0ZXJlZF9wYXNzZXMkTG9jYXRpb25ZKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHgyID0gdG9fc3RhdHNib21iJHgoZmlsdGVyZWRfcGFzc2VzJEVuZExvY2F0aW9uWCksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5MiA9IHRvX3N0YXRzYm9tYiR5KGZpbHRlcmVkX3Bhc3NlcyRFbmRMb2NhdGlvblkpKQ0KDQpnZ3Bsb3QocGFzc2VzX3Jlc2NhbGVkKSArDQogIGFubm90YXRlX3BpdGNoKGRpbWVuc2lvbnMgPSBwaXRjaF9zdGF0c2JvbWIsICwgY29sb3VyID0gIndoaXRlIiwgZmlsbCAgID0gInNwcmluZ2dyZWVuNCIpICsNCiAgZ2VvbV9zZWdtZW50KGFlcyh4ID0geCwgeSA9IHksIHhlbmQgPSB4MiwgeWVuZCA9IHkyKSwNCiAgICAgICAgICAgICAgIGNvbG91ciA9ICJjb3JhbCIsDQogICAgICAgICAgICAgICBhcnJvdyA9IGFycm93KGxlbmd0aCA9IHVuaXQoMC4yNSwgImNtIiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHR5cGUgPSAiY2xvc2VkIikpICsNCiAgIGdlb21fcG9pbnQoYWVzKHggPSB4LCB5ID0geSksDQogICAgICAgICAgICAgY29sb3VyID0gInllbGxvdyIsDQogICAgICAgICAgICAgc2l6ZSA9IDQpICsNCiAgdGhlbWVfcGl0Y2goKSArDQogIGRpcmVjdGlvbl9sYWJlbCh4X2xhYmVsID0gNjApICsNCiAgdGhlbWUocGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gInNwcmluZ2dyZWVuNCIpKSsNCiAgZ2d0aXRsZSgiUGFzc01hcCIsDQogICAgICAgICAgIk1lc3NpIGluIHRoZSBGaXJzdCBIYWxmIChOb3JtYWwgdGltZSkiKQ0KYGBgDQoNCiMjIFNlY29uZCBIYWxmIChOb3JtYWwgdGltZSkNCg0KYGBge3J9DQpmaWx0ZXJlZF9wYXNzZXMgPC0gcHJvY2Vzc19wZXJpb2QoMikNCnRvX3N0YXRzYm9tYiA8LSByZXNjYWxlX2Nvb3JkaW5hdGVzKGZyb20gPSBwaXRjaF9vcHRhLCB0byA9IHBpdGNoX3N0YXRzYm9tYikNCg0KcGFzc2VzX3Jlc2NhbGVkIDwtIGRhdGEuZnJhbWUoeCAgPSB0b19zdGF0c2JvbWIkeChmaWx0ZXJlZF9wYXNzZXMkTG9jYXRpb25YKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHkgID0gdG9fc3RhdHNib21iJHkoZmlsdGVyZWRfcGFzc2VzJExvY2F0aW9uWSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICB4MiA9IHRvX3N0YXRzYm9tYiR4KGZpbHRlcmVkX3Bhc3NlcyRFbmRMb2NhdGlvblgpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeTIgPSB0b19zdGF0c2JvbWIkeShmaWx0ZXJlZF9wYXNzZXMkRW5kTG9jYXRpb25ZKSkNCg0KZ2dwbG90KHBhc3Nlc19yZXNjYWxlZCkgKw0KICBhbm5vdGF0ZV9waXRjaChkaW1lbnNpb25zID0gcGl0Y2hfc3RhdHNib21iLCAsIGNvbG91ciA9ICJ3aGl0ZSIsIGZpbGwgICA9ICJzcHJpbmdncmVlbjQiKSArDQogIGdlb21fc2VnbWVudChhZXMoeCA9IHgsIHkgPSB5LCB4ZW5kID0geDIsIHllbmQgPSB5MiksDQogICAgICAgICAgICAgICBjb2xvdXIgPSAiY29yYWwiLA0KICAgICAgICAgICAgICAgYXJyb3cgPSBhcnJvdyhsZW5ndGggPSB1bml0KDAuMjUsICJjbSIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0eXBlID0gImNsb3NlZCIpKSArDQogICBnZW9tX3BvaW50KGFlcyh4ID0geCwgeSA9IHkpLA0KICAgICAgICAgICAgIGNvbG91ciA9ICJ5ZWxsb3ciLA0KICAgICAgICAgICAgIHNpemUgPSA0KSArDQogIHRoZW1lX3BpdGNoKCkgKw0KICBkaXJlY3Rpb25fbGFiZWwoeF9sYWJlbCA9IDYwKSArDQogIHRoZW1lKHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICJzcHJpbmdncmVlbjQiKSkrDQogIGdndGl0bGUoIlBhc3NNYXAiLA0KICAgICAgICAgICJNZXNzaSBpbiB0aGUgU2Vjb25kIEhhbGYgKE5vcm1hbCB0aW1lKSIpDQpgYGANCg0KIyMgRmlyc3QgSGFsZiAoRXh0cmEgdGltZSkNCg0KYGBge3J9DQpmaWx0ZXJlZF9wYXNzZXMgPC0gcHJvY2Vzc19wZXJpb2QoMykNCg0KdG9fc3RhdHNib21iIDwtIHJlc2NhbGVfY29vcmRpbmF0ZXMoZnJvbSA9IHBpdGNoX29wdGEsIHRvID0gcGl0Y2hfc3RhdHNib21iKQ0KDQpwYXNzZXNfcmVzY2FsZWQgPC0gZGF0YS5mcmFtZSh4ICA9IHRvX3N0YXRzYm9tYiR4KGZpbHRlcmVkX3Bhc3NlcyRMb2NhdGlvblgpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeSAgPSB0b19zdGF0c2JvbWIkeShmaWx0ZXJlZF9wYXNzZXMkTG9jYXRpb25ZKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHgyID0gdG9fc3RhdHNib21iJHgoZmlsdGVyZWRfcGFzc2VzJEVuZExvY2F0aW9uWCksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5MiA9IHRvX3N0YXRzYm9tYiR5KGZpbHRlcmVkX3Bhc3NlcyRFbmRMb2NhdGlvblkpKQ0KDQpnZ3Bsb3QocGFzc2VzX3Jlc2NhbGVkKSArDQogIGFubm90YXRlX3BpdGNoKGRpbWVuc2lvbnMgPSBwaXRjaF9zdGF0c2JvbWIsICwgY29sb3VyID0gIndoaXRlIiwgZmlsbCAgID0gInNwcmluZ2dyZWVuNCIpICsNCiAgZ2VvbV9zZWdtZW50KGFlcyh4ID0geCwgeSA9IHksIHhlbmQgPSB4MiwgeWVuZCA9IHkyKSwNCiAgICAgICAgICAgICAgIGNvbG91ciA9ICJjb3JhbCIsDQogICAgICAgICAgICAgICBhcnJvdyA9IGFycm93KGxlbmd0aCA9IHVuaXQoMC4yNSwgImNtIiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHR5cGUgPSAiY2xvc2VkIikpICsNCiAgIGdlb21fcG9pbnQoYWVzKHggPSB4LCB5ID0geSksDQogICAgICAgICAgICAgY29sb3VyID0gInllbGxvdyIsDQogICAgICAgICAgICAgc2l6ZSA9IDQpICsNCiAgdGhlbWVfcGl0Y2goKSArDQogIGRpcmVjdGlvbl9sYWJlbCh4X2xhYmVsID0gNjApICsNCiAgdGhlbWUocGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gInNwcmluZ2dyZWVuNCIpKSsNCiAgZ2d0aXRsZSgiUGFzc01hcCIsDQogICAgICAgICAgIk1lc3NpIGluIHRoZSBGaXJzdCBIYWxmIChFeHRyYSB0aW1lKSIpDQpgYGANCg0KIyMgU2Vjb25kIEhhbGYgKEV4dHJhIHRpbWUpDQoNCmBgYHtyfQ0KZmlsdGVyZWRfcGFzc2VzIDwtIHByb2Nlc3NfcGVyaW9kKDQpDQoNCnRvX3N0YXRzYm9tYiA8LSByZXNjYWxlX2Nvb3JkaW5hdGVzKGZyb20gPSBwaXRjaF9vcHRhLCB0byA9IHBpdGNoX3N0YXRzYm9tYikNCg0KcGFzc2VzX3Jlc2NhbGVkIDwtIGRhdGEuZnJhbWUoeCAgPSB0b19zdGF0c2JvbWIkeChmaWx0ZXJlZF9wYXNzZXMkTG9jYXRpb25YKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHkgID0gdG9fc3RhdHNib21iJHkoZmlsdGVyZWRfcGFzc2VzJExvY2F0aW9uWSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICB4MiA9IHRvX3N0YXRzYm9tYiR4KGZpbHRlcmVkX3Bhc3NlcyRFbmRMb2NhdGlvblgpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeTIgPSB0b19zdGF0c2JvbWIkeShmaWx0ZXJlZF9wYXNzZXMkRW5kTG9jYXRpb25ZKSkNCg0KZ2dwbG90KHBhc3Nlc19yZXNjYWxlZCkgKw0KICBhbm5vdGF0ZV9waXRjaChkaW1lbnNpb25zID0gcGl0Y2hfc3RhdHNib21iLCAsIGNvbG91ciA9ICJ3aGl0ZSIsIGZpbGwgICA9ICJzcHJpbmdncmVlbjQiKSArDQogIGdlb21fc2VnbWVudChhZXMoeCA9IHgsIHkgPSB5LCB4ZW5kID0geDIsIHllbmQgPSB5MiksDQogICAgICAgICAgICAgICBjb2xvdXIgPSAiY29yYWwiLA0KICAgICAgICAgICAgICAgYXJyb3cgPSBhcnJvdyhsZW5ndGggPSB1bml0KDAuMjUsICJjbSIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0eXBlID0gImNsb3NlZCIpKSArDQogICBnZW9tX3BvaW50KGFlcyh4ID0geCwgeSA9IHkpLA0KICAgICAgICAgICAgIGNvbG91ciA9ICJ5ZWxsb3ciLA0KICAgICAgICAgICAgIHNpemUgPSA0KSArDQogIHRoZW1lX3BpdGNoKCkgKw0KICBkaXJlY3Rpb25fbGFiZWwoeF9sYWJlbCA9IDYwKSArDQogIHRoZW1lKHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICJzcHJpbmdncmVlbjQiKSkrDQogIGdndGl0bGUoIlBhc3NNYXAiLA0KICAgICAgICAgICJNZXNzaSBpbiB0aGUgU2Vjb25kIEhhbGYgKEV4dHJhIHRpbWUpIikNCmBgYA0KDQojIyBQYXNzIFBvc2l0aW9uDQoNCiMjIyBBdmVyYWdlDQoNCmBgYHtyfQ0KY2xlYW5ldmVudHMgJT4lDQogIGZpbHRlcihwbGF5ZXIuaWQ9PXBsYXllcl9pZCAmIHR5cGUubmFtZSA9PSAiUGFzcyIpICU+JQ0KICBzb2NjZXJUcmFuc2Zvcm0obWV0aG9kPSdzdGF0c2JvbWInKSAlPiUNCiAgc29jY2VyUG9zaXRpb25NYXAoaWQgPSAicGxheWVyLm5hbWUiLCB4ID0gImxvY2F0aW9uLngiLCB5ID0gImxvY2F0aW9uLnkiLA0KICAgICAgICAgICAgICAgICAgICBmaWxsMSA9ICJibHVlIiwNCiAgICAgICAgICAgICAgICAgICAgYXJyb3cgPSAiciIsDQogICAgICAgICAgICAgICAgICAgIHRoZW1lID0gImdyYXNzIiwNCiAgICAgICAgICAgICAgICAgICAgdGl0bGUgPSAiQXZlcmFnZSBQYXNzIFBvc3RpdGlvbiIsDQogICAgICAgICAgICAgICAgICAgIHN1YnRpdGxlPSdNZXNzaSB2cyBGcmFuY2UnKQ0KDQpgYGANCg0KIyMjIEZhdm91cml0ZQ0KDQpgYGB7cn0NCiBjbGVhbmV2ZW50cyAlPiUNCiAgZmlsdGVyKHBsYXllci5pZCA9PSBwbGF5ZXJfaWQpICU+JQ0KICBzb2NjZXJQYXNzbWFwKGZpbGwgPSAibGlnaHRibHVlIiwgYXJyb3cgPSAiciIsIHRoZW1lPSdncmFzcycsDQogICAgICAgICAgICAgICAgdGl0bGUgPSAiSGlnaGVzdCBQYXNzIFBvc2l0aW9uIikNCmBgYA0KDQojIyBDb21wYXJpc29uDQoNCmBgYHtyfQ0KY2xlYW5ldmVudHMgJT4lDQogIGZpbHRlcih0ZWFtLm5hbWU9PSdBcmdlbnRpbmEnICYgdHlwZS5uYW1lID09ICJQYXNzIikgJT4lDQogIHNvY2NlclRyYW5zZm9ybShtZXRob2Q9J3N0YXRzYm9tYicpICU+JQ0KICBzb2NjZXJQb3NpdGlvbk1hcChpZCA9ICJwbGF5ZXIubmFtZSIsIHggPSAibG9jYXRpb24ueCIsIHkgPSAibG9jYXRpb24ueSIsDQogICAgICAgICAgICAgICAgICAgIGZpbGwxID0gImJsdWUiLA0KICAgICAgICAgICAgICAgICAgICBhcnJvdyA9ICJyIiwNCiAgICAgICAgICAgICAgICAgICAgdGhlbWUgPSAiZ3Jhc3MiLA0KICAgICAgICAgICAgICAgICAgICB0aXRsZSA9ICJBdmVyYWdlIFBhc3MgUG9zdGl0aW9uIiwNCiAgICAgICAgICAgICAgICAgICAgc3VidGl0bGU9J0FyZ2VudGluYScpDQoNCmBgYA0KDQojIFNob3RzDQoNCiMjIENvbXBhcmlzb24gYW1vbmcgdGVhbXMNCg0KYGBge3J9DQpzdW1tYXJ5IDwtIGNsZWFuZXZlbnRzICU+JQ0KICBncm91cF9ieSgiVGVhbSI9Y2xlYW5ldmVudHMkdGVhbS5uYW1lKSAlPiUNCiAgc3VtbWFyaXNlKHNob3RzID0gc3VtKHR5cGUubmFtZT09IlNob3QiLCBuYS5ybSA9IFRSVUUpKQ0Kc3VtbWFyeQ0KYGBgDQoNCiMjIENvbXBhcmlzb24gYW1vbmcgQXJnZW50aW5pYW4gcGxheWVycw0KDQpgYGB7cn0NCmZpbHRlcmVkX2V2ZW50cyA8LSBjbGVhbmV2ZW50cyAlPiUNCiAgZmlsdGVyKHRlYW0ubmFtZSA9PSAnQXJnZW50aW5hJyAmIHR5cGUubmFtZSA9PSAnU2hvdCcpICU+JQ0KICBzZWxlY3QocGxheWVyLm5hbWUpDQoNCnNob3RzIDwtIGZpbHRlcmVkX2V2ZW50cyAlPiUNCiAgZ3JvdXBfYnkocGxheWVyLm5hbWUpICU+JQ0KICBzdW1tYXJpc2Uoc2hvdHMgPSBuKCkpDQoNCnByaW50KHNob3RzKQ0KYGBgDQoNCiMjIE1lc3NpJ3MgU2hvdE1hcA0KDQotICAgMjMnIChQKQ0KLSAgIDEwOCcNCi0gICBTaG9vdG91dHMNCg0KYGBge3J9DQpjbGVhbmV2ZW50cyAlPiUNCiAgZmlsdGVyKHBsYXllci5pZD09cGxheWVyX2lkKSAlPiUNCiAgc29jY2VyVHJhbnNmb3JtKG1ldGhvZD0nc3RhdHNib21iJykgJT4lDQogIHNvY2NlclNob3RtYXAodGhlbWUgPSAiZ3Jhc3MiLCB0aXRsZSA9ICJTaG90TWFwIiwNCiAgICAgICAgICAgICAgICBzdWJ0aXRsZSA9ICJTaG90cyB0YWtlbiBieSBNZXNzaSIpDQpgYGANCg0KIyMgTWVzc2kncyBBdmVyYWdlIFNob3RNYXANCg0KYGBge3J9DQpjbGVhbmV2ZW50cyAlPiUNCiAgZmlsdGVyKHBsYXllci5pZD09cGxheWVyX2lkICYgdHlwZS5uYW1lID09ICJTaG90IikgJT4lDQogIHNvY2NlclRyYW5zZm9ybShtZXRob2Q9J3N0YXRzYm9tYicpICU+JQ0KICBzb2NjZXJQb3NpdGlvbk1hcChpZCA9ICJwbGF5ZXIubmFtZSIsIHggPSAibG9jYXRpb24ueCIsIHkgPSAibG9jYXRpb24ueSIsDQogICAgICAgICAgICAgICAgICAgIGZpbGwxID0gImJsdWUiLA0KICAgICAgICAgICAgICAgICAgICBhcnJvdyA9ICJyIiwNCiAgICAgICAgICAgICAgICAgICAgdGhlbWUgPSAiZ3Jhc3MiLA0KICAgICAgICAgICAgICAgICAgICB0aXRsZSA9ICJBdmVyYWdlIFNob3QgUG9zdGl0aW9uIiwNCiAgICAgICAgICAgICAgICAgICAgc3VidGl0bGUgPSAnTWVzc2kgdnMgRnJhbmNlJykNCmBgYA0KDQojIE1vdmVtZW50DQoNCiMjIEhlYXRNYXANCg0KYGBge3J9DQpjbGVhbmV2ZW50cyAlPiUNCiAgZmlsdGVyKHBsYXllci5pZD09cGxheWVyX2lkKSAlPiUNCiAgc29jY2VyVHJhbnNmb3JtKG1ldGhvZD0nc3RhdHNib21iJykgJT4lDQogIHNvY2NlckhlYXRtYXAoeCA9ICJsb2NhdGlvbi54IiwgeSA9ICJsb2NhdGlvbi55IiwgeEJpbnMgPSAyMSwgeUJpbnMgPSAxNCwNCiAgICAgICAgICAgICAgICB0aXRsZSA9ICJIZWF0TWFwIiwNCiAgICAgICAgICAgICAgICBzdWJ0aXRsZSA9ICdNZXNzaSB2cyBGcmFuY2UnKQ0KYGBgDQoNCiMgTW9kZWwNCg0KIyMgUHJlLXByb2Nlc3NpbmcNCg0KYGBge3J9DQpkYXRhIDwtIGNsZWFuZXZlbnRzICU+JQ0KICBzZWxlY3QoZHVyYXRpb24sIHBsYXllci5uYW1lKQ0KYGBgDQoNCmBgYHtyfQ0KZmlsdGVyZWRfZXZlbnRzX21vZGVsIDwtIGNsZWFuZXZlbnRzICU+JQ0KICBmaWx0ZXIodGVhbS5uYW1lPT0nQXJnZW50aW5hJykgJT4lDQogIHNlbGVjdChkdXJhdGlvbiwgcGxheWVyLm5hbWUpDQoNCmRhdGFfY2xlYW4gPC0gZmlsdGVyZWRfZXZlbnRzX21vZGVsW2NvbXBsZXRlLmNhc2VzKGZpbHRlcmVkX2V2ZW50c19tb2RlbCRkdXJhdGlvbiwgZmlsdGVyZWRfZXZlbnRzX21vZGVsJHBsYXllci5uYW1lKSwgXQ0KDQpwcmludChkYXRhX2NsZWFuKQ0KYGBgDQoNCmBgYHtyfQ0KZHVyYXRpb25fZGF0YSA8LSBkYXRhLmZyYW1lKHRvdGFsX2R1cmF0aW9uID0gZG91YmxlKCksIHBsYXllcl9uYW1lID0gY2hhcmFjdGVyKCksIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkNCmBgYA0KDQpgYGB7cn0NCnByb2Nlc3NfcGVyaW9kIDwtIGZ1bmN0aW9uKHBsYXllcl9uYW1lKSB7DQogIGZpbHRlcmVkX2V2ZW50cyA8LSBkYXRhX2NsZWFuICU+JQ0KICBmaWx0ZXIocGxheWVyLm5hbWU9PXBsYXllcl9uYW1lKSAlPiUNCiAgc2VsZWN0KGR1cmF0aW9uKQ0KDQogIHRvdGFsX2R1cmF0aW9uX3BsYXllciA8LSBzdW0oZmlsdGVyZWRfZXZlbnRzJGR1cmF0aW9uLCBuYS5ybSA9IFRSVUUpDQoNCiAgbmV3X3JvdyA8LSBkYXRhLmZyYW1lKHRvdGFsX2R1cmF0aW9uID0gdG90YWxfZHVyYXRpb25fcGxheWVyLCBwbGF5ZXJfbmFtZSA9IHBsYXllcl9uYW1lLCBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpDQogIHJldHVybiAobmV3X3JvdykNCn0NCmBgYA0KDQpgYGB7cn0NCnVuaXF1ZV9wbGF5ZXJzIDwtIHVuaXF1ZShkYXRhX2NsZWFuJHBsYXllci5uYW1lKQ0KDQoNCmZvciAocGxheWVyX25hbWUgaW4gdW5pcXVlX3BsYXllcnMpIHsNCiAgbmV3X3JvdyA8LSBwcm9jZXNzX3BlcmlvZChwbGF5ZXJfbmFtZSkNCiAgZHVyYXRpb25fZGF0YSA8LSByYmluZChkdXJhdGlvbl9kYXRhLCBuZXdfcm93KQ0KDQp9DQoNCnByaW50KGR1cmF0aW9uX2RhdGEpDQpgYGANCg0KYGBge3J9DQojVG90YWwgc2hvdHMgYnkgZWFjaCBwbGF5ZXINCnNob3RzIDwtIGNsZWFuZXZlbnRzICU+JQ0KICBmaWx0ZXIodGVhbS5uYW1lID09ICdBcmdlbnRpbmEnICYgdHlwZS5uYW1lID09ICdTaG90JykgJT4lDQogIGdyb3VwX2J5KHBsYXllci5uYW1lKSAlPiUNCiAgc3VtbWFyaXNlKHNob3RzID0gbigpKQ0KDQojIENyZWF0ZSBhIGRhdGEgZnJhbWUgd2l0aCBhbGwgcGxheWVyIG5hbWVzDQphbGxfcGxheWVycyA8LSBkYXRhLmZyYW1lKHBsYXllci5uYW1lID0gdW5pcXVlKGNsZWFuZXZlbnRzJHBsYXllci5uYW1lW2NsZWFuZXZlbnRzJHRlYW0ubmFtZSA9PSAnQXJnZW50aW5hJ10pKQ0KDQojIExlZnQgam9pbiB3aXRoIHNob3RzIHN1bW1hcnksIHJlcGxhY2UgTkEgd2l0aCAwDQptZXJnZWRfZGF0YSA8LSBsZWZ0X2pvaW4oYWxsX3BsYXllcnMsIHNob3RzLCBieSA9ICJwbGF5ZXIubmFtZSIpICU+JQ0KICBtdXRhdGUoc2hvdHMgPSBpZmVsc2UoaXMubmEoc2hvdHMpLCAwLCBzaG90cykpDQoNCiMgUHJpbnQgdGhlIHJlc3VsdA0KcHJpbnQobWVyZ2VkX2RhdGEpDQoNCmBgYA0KDQpgYGB7cn0NCiMgQXNzdW1pbmcgbWVyZ2VkX2RhdGEgYW5kIGR1cmF0aW9uX2RhdGEgYXJlIHlvdXIgZGF0YSBmcmFtZXMNCiMgbWVyZ2VkX2RhdGEgY29udGFpbnMgY29sdW1ucyBwbGF5ZXIubmFtZSBhbmQgc2hvdHMNCiMgZHVyYXRpb25fZGF0YSBjb250YWlucyBjb2x1bW5zIHBsYXllcl9uYW1lIGFuZCB0b3RhbF9kdXJhdGlvbg0KDQojIExlZnQgam9pbiBtZXJnZWRfZGF0YSB3aXRoIGR1cmF0aW9uX2RhdGENCmZpbmFsX21lcmdlZF9kYXRhIDwtIGxlZnRfam9pbihtZXJnZWRfZGF0YSwgZHVyYXRpb25fZGF0YSwgYnkgPSBjKCJwbGF5ZXIubmFtZSIgPSAicGxheWVyX25hbWUiKSkNCg0KIyBJZiBuZWVkZWQsIHJlcGxhY2UgTkEgdmFsdWVzIGluIHNob3RzIGFuZCB0b3RhbF9kdXJhdGlvbiB3aXRoIDANCmZpbmFsX21lcmdlZF9kYXRhJHNob3RzW2lzLm5hKGZpbmFsX21lcmdlZF9kYXRhJHNob3RzKV0gPC0gMA0KZmluYWxfbWVyZ2VkX2RhdGEkdG90YWxfZHVyYXRpb25baXMubmEoZmluYWxfbWVyZ2VkX2RhdGEkdG90YWxfZHVyYXRpb24pXSA8LSAwDQoNCiMgUHJpbnQgdGhlIGZpbmFsIG1lcmdlZCBkYXRhDQpwcmludChmaW5hbF9tZXJnZWRfZGF0YSkNCg0KDQpgYGANCg0KYGBge3J9DQojIEZpbHRlciBhbmQgY291bnQgcGFzc2VzDQpwYXNzZXMgPC0gY2xlYW5ldmVudHMgJT4lDQogIGZpbHRlcih0ZWFtLm5hbWUgPT0gJ0FyZ2VudGluYScgJiB0eXBlLm5hbWUgPT0gJ1Bhc3MnKSAlPiUNCiAgZ3JvdXBfYnkocGxheWVyLm5hbWUpICU+JQ0KICBzdW1tYXJpc2UocGFzc2VzID0gbigpKQ0KDQojIE1lcmdlIHdpdGggdGhlIGV4aXN0aW5nIGZpbmFsX21lcmdlZF9kYXRhDQpmaW5hbF9tZXJnZWRfZGF0YSA8LSBsZWZ0X2pvaW4oZmluYWxfbWVyZ2VkX2RhdGEsIHBhc3NlcywgYnkgPSBjKCJwbGF5ZXIubmFtZSIgPSAicGxheWVyLm5hbWUiKSkNCg0KIyBJZiBuZWVkZWQsIHJlcGxhY2UgTkEgdmFsdWVzIGluIHBhc3NlcyB3aXRoIDANCmZpbmFsX21lcmdlZF9kYXRhJHBhc3Nlc1tpcy5uYShmaW5hbF9tZXJnZWRfZGF0YSRwYXNzZXMpXSA8LSAwDQoNCiMgUHJpbnQgdGhlIGZpbmFsIG1lcmdlZCBkYXRhDQpwcmludChmaW5hbF9tZXJnZWRfZGF0YSkNCg0KYGBgDQoNCiMjIGstbWVhbnMgQ2x1c3RlcmluZw0KDQpgYGB7cn0NCnNlbGVjdGVkX2NvbHVtbnMgPC0gZmluYWxfbWVyZ2VkX2RhdGFbLCBjKCJzaG90cyIsICJ0b3RhbF9kdXJhdGlvbiIsICJwYXNzZXMiKV0NCg0KIyBTdGFuZGFyZGl6ZSB0aGUgZGF0YQ0Kc2NhbGVkX2RhdGEgPC0gc2NhbGUoc2VsZWN0ZWRfY29sdW1ucykNCg0KayA8LSAzICANCmttZWFuc19yZXN1bHQgPC0ga21lYW5zKHNjYWxlZF9kYXRhLCBjZW50ZXJzID0gaywgbnN0YXJ0ID0gMjApDQoNCiMgQWRkIHRoZSBjbHVzdGVyIGFzc2lnbm1lbnRzIGJhY2sgdG8gdGhlIG9yaWdpbmFsIGRhdGEgZnJhbWUNCmZpbmFsX21lcmdlZF9kYXRhJGNsdXN0ZXIgPC0ga21lYW5zX3Jlc3VsdCRjbHVzdGVyDQoNCiMgUHJpbnQgdGhlIGNsdXN0ZXIgY2VudHJvaWRzDQpwcmludChrbWVhbnNfcmVzdWx0JGNlbnRlcnMpDQoNCiMgVmlldyB0aGUgZGlzdHJpYnV0aW9uIG9mIHBsYXllcnMgaW4gZWFjaCBjbHVzdGVyDQp0YWJsZShmaW5hbF9tZXJnZWRfZGF0YSRjbHVzdGVyKQ0KDQpgYGANCg0KYGBge3J9DQojIFNjYXR0ZXIgcGxvdCBvZiB0b3RhbF9kdXJhdGlvbiBhZ2FpbnN0IHNob3RzLCBjb2xvcmVkIGJ5IGNsdXN0ZXIsIHdpdGggcGxheWVyIG5hbWVzDQpnZ3Bsb3QoZmluYWxfbWVyZ2VkX2RhdGEsIGFlcyh4ID0gdG90YWxfZHVyYXRpb24sIHkgPSBzaG90cywgY29sb3IgPSBmYWN0b3IoY2x1c3RlciksIGxhYmVsID0gcGxheWVyLm5hbWUpKSArDQogIGdlb21fcG9pbnQoYWVzKHNpemUgPSBwYXNzZXMpKSArDQogIGdlb21fdGV4dChudWRnZV94ID0gMC4yLCBudWRnZV95ID0gMC4yLCBzaXplID0gMykgKyAgIyBBZGp1c3QgbnVkZ2UgdmFsdWVzIGFzIG5lZWRlZA0KICBsYWJzKHRpdGxlID0gIkstTWVhbnMgQ2x1c3RlcmluZyBvZiBQbGF5ZXJzIiwgeCA9ICJUb3RhbCBEdXJhdGlvbiIsIHkgPSAiU2hvdHMiLCBjb2xvciA9ICJDbHVzdGVyIikgKw0KICB0aGVtZV9taW5pbWFsKCkNCg0KIyBEcmF3IGNpcmNsZXMgYXJvdW5kIGVhY2ggY2x1c3Rlcg0KY2x1c3Rlcl9jZW50ZXJzIDwtIGFzLmRhdGEuZnJhbWUoa21lYW5zX3Jlc3VsdCRjZW50ZXJzWywgYygidG90YWxfZHVyYXRpb24iLCAic2hvdHMiKV0pDQpgYGANCg0KYGBge3J9DQoNCmdncGxvdChmaW5hbF9tZXJnZWRfZGF0YSwgYWVzKHggPSBwYXNzZXMsIHkgPSBzaG90cywgY29sb3IgPSBmYWN0b3IoY2x1c3RlciksIGxhYmVsID0gcGxheWVyLm5hbWUpKSArDQogIGdlb21fcG9pbnQoYWVzKHNpemUgPSB0b3RhbF9kdXJhdGlvbikpICsNCiAgZ2VvbV90ZXh0KG51ZGdlX3ggPSAwLjIsIG51ZGdlX3kgPSAwLjIsIHNpemUgPSAzKSArICAjIEFkanVzdCBudWRnZSB2YWx1ZXMgYXMgbmVlZGVkDQogIGxhYnModGl0bGUgPSAiSy1NZWFucyBDbHVzdGVyaW5nIG9mIFBsYXllcnMiLCB4ID0gIlBhc3NlcyIsIHkgPSAiU2hvdHMiLCBjb2xvciA9ICJDbHVzdGVyIikgKw0KICB0aGVtZV9taW5pbWFsKCkNCg0KDQpjbHVzdGVyX2NlbnRlcnMgPC0gYXMuZGF0YS5mcmFtZShrbWVhbnNfcmVzdWx0JGNlbnRlcnNbLCBjKCJwYXNzZXMiLCAic2hvdHMiKV0pDQpgYGANCg0KYGBge3J9DQpsaWJyYXJ5KHBsb3RseSkNCg0KIyBDcmVhdGUgYSAzRCBzY2F0dGVyIHBsb3QNCnAgPC0gcGxvdF9seShmaW5hbF9tZXJnZWRfZGF0YSwgeCA9IH50b3RhbF9kdXJhdGlvbiwgeSA9IH5zaG90cywgeiA9IH5wYXNzZXMsIGNvbG9yID0gfmZhY3RvcihjbHVzdGVyKSkgJT4lDQogIGFkZF9tYXJrZXJzKHNpemUgPSAyKSAgJT4lDQoNCiMgQWRkIGxhYmVscyBmb3IgcGxheWVyIG5hbWVzDQogIGFkZF90ZXh0KHRleHQgPSB+cGxheWVyLm5hbWUsIA0KICAgICAgICAgICBzaG93bGVnZW5kID0gRkFMU0UsDQogICAgICAgICAgIGhvdmVyaW5mbyA9ICJ0ZXh0IiwNCiAgICAgICAgICAgc2l6ZSA9IDEpDQojIFNob3cgdGhlIHBsb3QNCnANCg0KDQoNCmBgYA0K